﻿#If WINDOWS Then

Imports System.Collections.ObjectModel
Imports System.Speech
Imports System.Speech.Synthesis
Imports System.Speech.AudioFormat
Imports System.Globalization

Namespace Global.System.Speech

    Public Class TTSWaveSpeaker
        Protected Friend WithEvents tts As New SpeechSynthesizer

        Public Event SpeakCompeleted(ByVal sender As Object, ByVal e As SpeakCompletedEventArgs)

        Private m_InstalledVoices As ReadOnlyCollection(Of InstalledVoice)
        Private m_Formats As ReadOnlyCollection(Of AudioFormat.SpeechAudioFormatInfo)

        Public ReadOnly Property TTSEngine() As SpeechSynthesizer
            Get
                Return Me.tts
            End Get
        End Property

        Public ReadOnly Property InstalledVoices() As ReadOnlyCollection(Of InstalledVoice)
            Get
                Return Me.m_InstalledVoices
            End Get
        End Property

        Public ReadOnly Property Formats() As ReadOnlyCollection(Of SpeechAudioFormatInfo)
            Get
                Return Me.m_Formats
            End Get
        End Property

        Public Property AsyncCall() As Boolean = False
        Public Property FileName() As String = ""
        Public Property Result() As Byte()

        Public Sub New()
            m_InstalledVoices = tts.GetInstalledVoices()
            m_Formats = GetAllFormats()
        End Sub

        Public Sub New(ByVal fn As String)
            m_InstalledVoices = tts.GetInstalledVoices()
            m_Formats = GetAllFormats()
            Me.FileName = fn
        End Sub

        Public Function GetVoiceByCulture(ByVal ci As System.Globalization.CultureInfo) As String
            For Each v In Me.InstalledVoices
                If v.VoiceInfo.Culture.LCID = ci.LCID Then
                    Return v.VoiceInfo.Name
                End If
            Next
            Return ""
        End Function

        Public Function GetVoiceByName(ByVal keyword As String, Optional ByVal predicate As Func(Of String, String, Boolean) = Nothing) As String
            If predicate Is Nothing Then
                predicate =
                    Function(item1 As String, item2 As String)
                        Return item1.Contains(item2)
                    End Function
            End If
            Dim voices = Me.m_InstalledVoices.Where(
                Function(iv As InstalledVoice) As Boolean
                    Return predicate(iv.VoiceInfo.Name, keyword)
                End Function)
            If voices.Count() > 0 Then
                Return voices.First().VoiceInfo.Name
            Else
                If Me.m_InstalledVoices.Count > 0 Then
                    Return Me.m_InstalledVoices.First().VoiceInfo.Name
                Else
                    Return ""
                End If
            End If
        End Function

        Public Function Speak(ByVal txt As String) As Boolean
            Return Me.Speak(txt, Me.GetVoiceByCulture(New CultureInfo("zh-CN")), PromptRate.NotSet, Me.Formats(0))
        End Function

        Public Function SpeakAsync(ByVal txt As String) As Boolean
            Return Me.SpeakAsync(txt, Me.GetVoiceByCulture(New CultureInfo("zh-CN")), PromptRate.NotSet, Me.Formats(0))
        End Function

        Public Function Speak(ByVal txt As String, ByVal keyword As String) As Boolean
            Return Me.Speak(txt, Me.GetVoiceByName(keyword), PromptRate.NotSet, Me.Formats(0))
        End Function

        Public Function SpeakAsync(ByVal txt As String, ByVal keyword As String) As Boolean
            Return Me.SpeakAsync(txt, Me.GetVoiceByName(keyword), PromptRate.NotSet, Me.Formats(0))
        End Function

        Public Function Speak(ByVal txt As String, ByVal voice As String, ByVal rate As PromptRate, ByVal fmt As SpeechAudioFormatInfo) As Boolean
            Try
                Me.AsyncCall = False
                Dim result = Me.m_InstalledVoices.Where(
                    Function(iv As InstalledVoice) As Boolean
                        Return iv.VoiceInfo.Name = voice
                    End Function)
                If result.Count() > 0 Then
                    Me.FileName = ""
                    tts.SelectVoice(voice)
                    tts.Rate = rate
                    tts.SetOutputToDefaultAudioDevice()
                    tts.Speak(txt)
                    tts.SpeakAsyncCancelAll()
                    tts.SetOutputToNull()
                    Return True
                Else
                    Return False
                End If
            Catch ex As Exception
                Throw ex
            End Try
        End Function

        Public Function SpeakAsync(ByVal txt As String, ByVal voice As String, ByVal rate As PromptRate, ByVal fmt As SpeechAudioFormatInfo) As Boolean
            Try
                Me.AsyncCall = True
                Dim result = Me.m_InstalledVoices.Where(
                    Function(iv As InstalledVoice) As Boolean
                        Return iv.VoiceInfo.Name = voice
                    End Function)
                If result.Count() > 0 Then
                    Me.FileName = ""
                    Try
                        tts.SelectVoice(voice)
                        tts.Rate = rate
                        tts.SetOutputToDefaultAudioDevice()
                    Catch ex As Exception
                        Exit Try
                    End Try
                    tts.SpeakAsync(txt)
                    Return True
                Else
                    Return False
                End If
            Catch ex As Exception
                Throw ex
            End Try
        End Function

        Public Function BeginSpeak(ByVal txt As String, ByVal voice As String, ByVal rate As PromptRate, ByVal fmt As SpeechAudioFormatInfo, ByVal callback As AsyncCallback, ByVal state As Object) As IAsyncResult
            Dim act As New System.Func(Of Object(), Byte())(
                Function(p() As Object) As Byte()
                    Dim tts As New TTSWaveSpeaker(p(0).ToString())
                    tts.Speak(p(1), p(2), p(3), p(4))
                    Return tts.Result
                End Function)
            Dim parm() As Object = {Me.FileName, txt, voice, rate, fmt}
            Return act.BeginInvoke(parm, callback, state)
        End Function

        Public Function SpeakToFile(ByVal txt As String, ByVal voice As String, ByVal rate As PromptRate, ByVal fmt As SpeechAudioFormatInfo) As Boolean
            Try
                Me.AsyncCall = False
                Dim result = Me.m_InstalledVoices.Where(
                    Function(iv As InstalledVoice) As Boolean
                        Return iv.VoiceInfo.Name = voice
                    End Function)
                If result.Count() > 0 Then
                    tts.SelectVoice(voice)
                    tts.Rate = rate
                    tts.SetOutputToWaveFile(Me.FileName, fmt)
                    tts.Speak(txt)
                    tts.SpeakAsyncCancelAll()
                    tts.SetOutputToNull()
                    Return True
                Else
                    Return False
                End If
            Catch ex As Exception
                Throw ex
            End Try
        End Function

        Public Function SpeakToFileAsync(ByVal txt As String, ByVal voice As String, ByVal rate As PromptRate, ByVal fmt As SpeechAudioFormatInfo) As Boolean
            Try
                Me.AsyncCall = True
                Dim result = Me.m_InstalledVoices.Where(
                    Function(iv As InstalledVoice) As Boolean
                        Return iv.VoiceInfo.Name = voice
                    End Function)
                If result.Count() > 0 Then
                    tts.SelectVoice(voice)
                    tts.Rate = rate
                    tts.SetOutputToWaveFile(Me.FileName, fmt)
                    tts.SpeakAsync(txt)
                    Return True
                Else
                    Return False
                End If
            Catch ex As Exception
                Throw ex
            End Try
        End Function

        Public Function BeginSpeakToFile(ByVal txt As String, ByVal voice As String, ByVal rate As PromptRate, ByVal fmt As SpeechAudioFormatInfo, ByVal callback As AsyncCallback, ByVal state As Object) As IAsyncResult
            Dim act As New System.Func(Of Object(), Byte())(
                Function(p() As Object) As Byte()
                    Dim tts As New TTSWaveSpeaker(p(0).ToString())
                    tts.SpeakToFile(p(1), p(2), p(3), p(4))
                    Return tts.Result
                End Function)
            Dim parm() As Object = {Me.FileName, txt, voice, rate, fmt}
            Return act.BeginInvoke(parm, callback, state)
        End Function

        Public Sub SpeakAsyncCancelAll()
            Me.tts.SpeakAsyncCancelAll()
        End Sub

        Private Function GetAllFormats() As ReadOnlyCollection(Of SpeechAudioFormatInfo)
            Dim lst As New Generic.List(Of SpeechAudioFormatInfo)
            For i As Integer = 16 To 48 Step 8
                For j As Integer = 8 To 16 Step 8
                    For k As Integer = 1 To 2
                        Dim a As New SpeechAudioFormatInfo(i * 1024, j, k)
                        lst.Add(a)
                    Next k
                Next j
            Next i
            Return New ReadOnlyCollection(Of SpeechAudioFormatInfo)(lst)
        End Function

        Private Sub tts_SpeakCompleted(sender As Object, e As SpeakCompletedEventArgs) Handles tts.SpeakCompleted
            If e.Error IsNot Nothing Then
                If Debugger.IsAttached Then
#If DEBUG Then
                    'Nop
#Else
                    Throw e.Error
#End If
                Else
                    Debug.WriteLine(e.Error.Message)
                End If
            Else
                If Me.AsyncCall Then
                    If String.IsNullOrEmpty(Me.FileName) = False Then
                        tts.SetOutputToNull()
                        Using fs As New FileStream(Me.FileName, FileMode.Open)
                            Using ms As New MemoryStream()
                                fs.CopyTo(ms)
                                ms.Flush()
                                Me.Result = ms.ToArray()
                            End Using
                        End Using
                        System.IO.File.Delete(Me.FileName)
                    End If
                End If
            End If
            RaiseEvent SpeakCompeleted(Me, e)
        End Sub

    End Class

End Namespace

#End If